/**
 * @file frame-injector.ts
 * @description Inject a minimal password-capture content script into every frame (including cross-origin)
 *              using WebFrameMain APIs. The injected script runs early and communicates with the top
 *              window via postMessage.
 */

import type { WebContents, WebFrameMain } from 'electron'

import { existsSync, readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { resolve } from 'node:path'

let INJECTION_CODE_CACHE: string | null = null

function getInjectionCode(): string {
  if (INJECTION_CODE_CACHE) return INJECTION_CODE_CACHE

  const attempted: string[] = []

  // 1) Preferred: file placed next to compiled frame-injector.js in dist
  try {
    const codeUrl = new URL('paf-inject.js', import.meta.url)
    const codePath = fileURLToPath(codeUrl)
    attempted.push(codePath)
    if (existsSync(codePath)) {
      INJECTION_CODE_CACHE = readFileSync(codePath, 'utf8')
      return INJECTION_CODE_CACHE
    }
  } catch {
    // ignore and try next locations
  }

  // 2) Dev fallback: read directly from project source (when running via Vite dev/build without copied asset)
  try {
    const codePathDev = resolve(process.cwd(), 'srcMain/modules/paf-inject.js')
    attempted.push(codePathDev)
    if (existsSync(codePathDev)) {
      INJECTION_CODE_CACHE = readFileSync(codePathDev, 'utf8')
      return INJECTION_CODE_CACHE
    }
  } catch {
    // ignore and fall through
  }

  console.warn('[FrameInjector] failed to load injection code. Tried:', attempted)
  INJECTION_CODE_CACHE = '(()=>{ try { console.warn("[PAF Inject] missing code"); } catch {} })()'
  return INJECTION_CODE_CACHE
}

/** Inject into a specific frame (idempotent inside the frame) */
export async function injectPasswordAutofillIntoFrame(frame: WebFrameMain): Promise<void> {
  try {
    const execIso = (frame as any).executeJavaScriptInIsolatedWorld as
      | undefined
      | ((
          worldId: number,
          scripts: Array<{
            code: string
            url?: string
          }>
        ) => Promise<any>)
    await (typeof execIso === 'function'
      ? execIso.call(frame, 777, [{ code: getInjectionCode(), url: 'fieldano://paf-inject.js' }])
      : frame.executeJavaScript(getInjectionCode()))

    // console.log('[FrameInjector] injected OK', {
    //   url: (frame as any).url,
    //   frameId: (frame as any).routingId
    // })
  } catch (error) {
    console.warn('[FrameInjector] inject failed', error)
  }
}

/** Find a WebFrameMain by routingId in the frame tree */
function findFrameByRoutingId(root: WebFrameMain, routingId: number): WebFrameMain | null {
  if ((root as any).routingId === routingId) return root

  try {
    const children: any = (root as any).frames
    if (children && Array.isArray(children)) {
      for (const c of children as WebFrameMain[]) {
        const found = findFrameByRoutingId(c, routingId)
        if (found) return found
      }
    }
  } catch {
    /* empty */
  }
  return null
}

/** Attach injector to all frames of a WebContents */
export function attachPasswordAutofillInjector(wc: WebContents): void {
  // Inject for future frames
  wc.on('frame-created', (_event, details: { frame: WebFrameMain | null }) => {
    const frame = details.frame
    if (!frame) return
    // Avoid injecting into main frame to reduce duplicate prompts; only child frames when possible
    try {
      const parent = (frame as any).parent
      if (parent === null || parent === undefined) {
        return
      }
    } catch {
      /* empty */
    }
    // Inject immediately; the script defers its own init to DOMContentLoaded

    void injectPasswordAutofillIntoFrame(frame)
  })

  // Re-inject after any frame finishes loading a new document
  wc.on(
    'did-frame-finish-load',
    (_event, isMainFrame: boolean, _frameProcessId: number, frameRoutingId: number) => {
      try {
        if (isMainFrame) return
        const root = wc.mainFrame
        if (!root) return
        const frame = findFrameByRoutingId(root, frameRoutingId)
        if (!frame) {
          return
        }

        void injectPasswordAutofillIntoFrame(frame)
      } catch {
        /* empty */
      }
    }
  )
}
